package com.siteseat.crudeq.broker.transport;

import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;

import com.siteseat.crudeq.broker.BrokerConstants;
import com.siteseat.common.foundation.PropertyHelper;
import com.siteseat.common.security.SecureComponent;
import com.siteseat.crudeq.kernel.QueueManager;

/**
 *
 */
public class BrokerSessionManager extends SecureComponent implements BrokerConstants {

  private Map<Integer, BrokerSession> sessionsByInternalId        = new ConcurrentHashMap<Integer, BrokerSession>();

  private BrokerSessionFactory        factory;

  private QueueManager                queueManager;

  // 
  private Timer                       sessionCheckTimer;

  private int                         sessionCheckPeriodBySeconds = DEFAULT_SESSION_CHECK_PERIOD_BY_SECONDS;

  /**
   * @param factory
   */
  public BrokerSessionManager( BrokerSessionFactory factory, QueueManager queueManager ) {
    this.factory = factory;
    this.queueManager = queueManager;
  }

  /**
   * Gets the factory
   * 
   * @return the factory
   */
  public BrokerSessionFactory getFactory() {
    return factory;
  }

  /**
   * Gets the sessionCheckPeriodBySeconds
   * 
   * @return the sessionCheckPeriodBySeconds
   */
  public int getSessionCheckPeriodBySeconds() {
    return sessionCheckPeriodBySeconds;
  }

  /**
   * Sets the sessionCheckPeriodBySeconds
   * 
   * @param sessionCheckPeriodBySeconds
   *          the sessionCheckPeriodBySeconds to set
   */
  public void setSessionCheckPeriodBySeconds( int sessionCheckPeriodBySeconds ) {
    this.sessionCheckPeriodBySeconds = sessionCheckPeriodBySeconds;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void doInit() throws Exception {
    super.doInit();
    
    PropertyHelper.ensureInitialized( log, getFactory(), "factory" );

    {
      long period = getSessionCheckPeriodBySeconds();
      PropertyHelper.ensurePositive( log, period, "sessionCheckPeriodBySeconds" );
      period *= 1000;
      
      sessionCheckTimer = new Timer( "Session Check Timer", true );
      sessionCheckTimer.schedule( new TimerTask() {

        @Override
        public void run() {
          removeTimeoutedSessions();
        }

      }, period, period );
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void doDestroy() throws Exception {
    // do nothing so far
  }

  /**
   * 
   */
  protected void removeTimeoutedSessions() {
    long period = getSessionCheckPeriodBySeconds() * 1000;
    long now = System.currentTimeMillis();

    for( BrokerSession s: sessionsByInternalId.values() ) {
      long last = s.getLastAccessTimeByMilliseconds();
      if( ( now - last ) > period ) {
        remove( s.getInternalId() );
      }
    }
  }

  /**
   * @param internalId
   * @throws InvalidSessionException
   */
  public BrokerSession create( Object transferObject ) {
    BrokerSession r = getFactory().create( transferObject, getSecurityManager(), queueManager );
    Integer internalId = r.getInternalId();
    BrokerSession old = this.sessionsByInternalId.put( internalId, r );
    if( old != null ) {
      log.warn( "Duplicated session id: " + internalId + "; The old one will be removed" );
      old.close();
    }

    return r;
  }

  /**
   * @param internalId
   * @throws InvalidSessionException
   */
  public BrokerSession get( Integer internalId ) {
    return this.sessionsByInternalId.get( internalId );
  }

  /**
   * @param internalId
   * @throws InvalidRequestException
   */
  public void remove( Integer internalId ) {
    BrokerSession s = this.sessionsByInternalId.remove( internalId );
    if( s != null ) {
      s.close();
    }
  }

}
